/**
 * 按域（Domain）调度的通用工具。
 *
 * 将待处理项按域分组并调度：
 * - 跨域：并行执行，最大并行域数由 `maxDomainsAtOnce` 限制
 * - 同一域：串行执行，项间间隔 `sameDomainDelayMs` 毫秒
 *
 * 与 UI/框架无关，可在任意环境复用。
 */
export interface DomainSchedulerOptions<T> {
  /** 同时处理的跨域最大数量；默认无限制。 */
  maxDomainsAtOnce?: number
  /** 同一域内相邻两项之间的延迟（毫秒），默认为 0。 */
  sameDomainDelayMs?: number
  /** 域开始处理时的可选回调。 */
  onDomainStart?: (domain: string, items: readonly T[]) => void | Promise<void>
  /** 域结束处理时的可选回调。 */
  onDomainEnd?: (domain: string, items: readonly T[]) => void | Promise<void>
  /** 可选的中止信号。 */
  signal?: AbortSignal
}

/**
 * 按域调度：限制跨域并行度，并对同域应用延迟。
 */
export async function scheduleByDomain<T>(
  items: readonly T[],
  getDomain: (item: T) => string,
  runOne: (item: T) => void | Promise<void>,
  opts: DomainSchedulerOptions<T> = {}
): Promise<void> {
  const signal = opts.signal
  const throwIfAborted = () => {
    if (signal?.aborted) throw new DOMException('Aborted', 'AbortError')
  }
  throwIfAborted()

  // 按域分组
  const byDomain = new Map<string, T[]>()
  for (const it of items) {
    const dRaw = getDomain(it)
    const d = dRaw && dRaw.trim() ? dRaw.trim().toLowerCase() : 'unknown'
    const arr = byDomain.get(d)
    if (arr) arr.push(it)
    else byDomain.set(d, [it])
  }

  const domains = Array.from(byDomain.keys())
  const max = Number.isFinite(opts.maxDomainsAtOnce as number)
    ? Math.max(1, Number(opts.maxDomainsAtOnce))
    : Number.POSITIVE_INFINITY
  const delayMs = Math.max(0, Number(opts.sameDomainDelayMs || 0))

  const sleep = (ms: number) => new Promise<void>(r => setTimeout(r, ms))

  const runDomain = async (domain: string) => {
    const arr = byDomain.get(domain) ?? []
    if (opts.onDomainStart) await opts.onDomainStart(domain, arr)
    for (const item of arr) {
      throwIfAborted()
      await runOne(item)
      if (delayMs > 0) await sleep(delayMs)
    }
    if (opts.onDomainEnd) await opts.onDomainEnd(domain, arr)
  }

  // 通过工作池限制跨域并发
  const queue = [...domains]
  const worker = async () => {
    while (queue.length > 0) {
      throwIfAborted()
      const d = queue.shift()!
      await runDomain(d)
    }
  }
  const workersCount = Math.min(max === Infinity ? queue.length : max, queue.length)
  const workers = Array.from({ length: workersCount }, () => worker())
  await Promise.all(workers)
}
